package vip.xls8z.webtools.localizable;

import lombok.Cleanup;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@Slf4j
@Controller
public class ParseLocalizableController {

    private static final String OUTPUT_FOLDER = "output/";

    private static final String TEMPLATE_NAME = "Localizable-Template.xlsx";
    private static final String TEMPLATE_PATH = "templates/" + TEMPLATE_NAME;

    private static final String ANNOTATION_ZH_CN = "zh-Hans";

    private static final List<String> SUPPORT_LANGUAGES = Arrays.asList(
            "en", "en-GB", "en-AU", "en-CA", "en-IN", "fr", "fr-CA", "es", "es-MX", "pt", "pt-BR", "it", "de",
            "zh-Hans", "zh-Hant", "zh-HK", "nl", "ja", "ko", "vi", "ru", "sv", "da", "fi", "nb", "tr", "el", "id",
            "ms", "th", "hi", "hu", "pl", "cs", "sk", "uk", "ca", "ro", "hr", "he", "ar"
    );

    @GetMapping("/")
    public String index() {
        return "/localizable-parser.html";
    }

    @GetMapping("/download/template")
    public void downloadTemplate(HttpServletRequest request, HttpServletResponse response) throws Exception {
        InputStream is = getClass().getClassLoader().getResourceAsStream(TEMPLATE_PATH);
        download(TEMPLATE_NAME, is, request, response);
    }

    @PostMapping("/upload/{type}")
    public void upload(@PathVariable Integer type,
                       @RequestParam("refFile") MultipartFile refFile,
                       @RequestParam("file") MultipartFile file,
                       HttpServletRequest request, HttpServletResponse response) throws Exception {
        if (file == null) {
            return;
        }
        log.info("refFile: {}, file: {}", refFile.getOriginalFilename(), file.getOriginalFilename());
        readAndParsed(type, file.getOriginalFilename(), file.getInputStream(), refFile, request, response);
        log.info("end");
    }

    private Map<String, String> readRefFile(MultipartFile refFile) throws IOException {
        if (refFile == null) {
            return null;
        }
        Map<String, String> refMap = new LinkedHashMap<>();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(refFile.getInputStream()));
        String lineTxt;
        while ((lineTxt = bufferedReader.readLine()) != null) {
            if (!lineTxt.contains("=") || !lineTxt.contains(";")) {
                continue;
            }
            int lastIdx = lineTxt.indexOf(";");
            String keyValue = lineTxt.substring(0, lastIdx);
            String[] keyVals = keyValue.split("=");
            String key = keyVals[0].trim().replace("\"", "");
            String val = keyVals[1].trim().replace("\"", "");
            log.info("key:{}, val:{}", key, val);
            refMap.put(key, val);
        }
        return refMap;
    }


    @SuppressWarnings("deprecation")
    public void readAndParsed(Integer type, String filename, InputStream fip, MultipartFile refFile, HttpServletRequest request, HttpServletResponse response) throws Exception {
        log.info("start, parsing: {}", filename);
        String name = filename.substring(0, filename.lastIndexOf(".")) + (type == 1 ? "-ios" : "-android");
        if (!filename.endsWith(".xls") && !filename.endsWith(".xlsx")) {
            return;
        }
        log.info("name: {}", name);

        Sheet sheet;
        if (filename.endsWith(".xls")) {
            HSSFWorkbook xlsWb = new HSSFWorkbook(fip);
            sheet = xlsWb.getSheetAt(0);
        } else {
            XSSFWorkbook workbook = new XSSFWorkbook(fip);
            sheet = workbook.getSheetAt(0);
        }


        int rowNum = sheet.getPhysicalNumberOfRows();
        Row head = sheet.getRow(0);

        Map<String, Map<String, String>> languageKeyValMap = new LinkedHashMap<>();

        Map<String, Map<String, String>> refZhCNMap = new LinkedHashMap<>();

        for (int i = 1; i < rowNum; i++) {
            Row row = sheet.getRow(i);
            if (row == null) {
                continue;
            }

            // 确认 - cancel - xxx
            int zhIdx = -1;
            for (int j = head.getFirstCellNum() + 1; j < head.getLastCellNum(); j++) {
                Cell languageCell = head.getCell(j);
                languageCell.setCellType(CellType.STRING);
                String language = languageCell.getStringCellValue().trim();
                if (ANNOTATION_ZH_CN.equals(language)) {
                    zhIdx = j;
                    break;
                }
            }

            if (zhIdx == -1) {
                break;
            }

            Cell zhCell = row.getCell(zhIdx);
            zhCell.setCellType(CellType.STRING);
            String zh = zhCell.getStringCellValue().trim();

            for (int j = head.getFirstCellNum() + 1; j < head.getLastCellNum(); j++) {
                Cell languageCell = head.getCell(j);
                languageCell.setCellType(CellType.STRING);
                String language = languageCell.getStringCellValue().trim();
                if (ANNOTATION_ZH_CN.equals(language)) {
                    continue;
                }

                Cell languageCellVal = row.getCell(j);
                languageCellVal.setCellType(CellType.STRING);
                String languageVal = languageCellVal.getStringCellValue().trim();

                Map<String, String> keyValMap = refZhCNMap.computeIfAbsent(language, k -> new LinkedHashMap<>());
                keyValMap.put(zh, languageVal);
            }
        }

        for (int i = 1; i < rowNum; i++) {
            Row row = sheet.getRow(i);
            if (row != null) {
                Cell cellKey = row.getCell(0);
                cellKey.setCellType(CellType.STRING);
                String key = cellKey.getStringCellValue().trim();
                for (int j = head.getFirstCellNum() + 1; j < head.getLastCellNum(); j++) {
                    Cell languageCell = head.getCell(j);
                    languageCell.setCellType(CellType.STRING);
                    String language = languageCell.getStringCellValue().trim();
                    if (!SUPPORT_LANGUAGES.contains(language)) {
                        continue;
                    }

                    Cell languageValCell = row.getCell(j);
                    languageValCell.setCellType(CellType.STRING);
                    String languageVal = languageValCell.getStringCellValue().trim();

                    Map<String, String> keyValMap = languageKeyValMap.computeIfAbsent(language, k -> new LinkedHashMap<>());
                    keyValMap.put(key, languageVal);
                }
            }
        }

        String timeTag = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
        String threadName = Thread.currentThread().getName();
        String outputFolder = OUTPUT_FOLDER + timeTag + "_" + threadName + "/" + name + "/";

        Map<String, String> zhCNMap = languageKeyValMap.get(ANNOTATION_ZH_CN);

        String formatIOS = "\"%s\" = \"%s\"; // %s";
        String formatAndroid = "\t<string name=\"%s\">%s</string>";

        for (Map.Entry<String, Map<String, String>> entry : languageKeyValMap.entrySet()) {
            String language = entry.getKey();
            List<String> keyValList = new ArrayList<>();
            Map<String, String> keyValMap = entry.getValue();

            if (type == 1) {
                String langFolder = outputFolder + language + ".lproj/";

                String launchScreen_string = langFolder + "result.strings";
                File launchScreen_string_file = new File(launchScreen_string);
                createFile(launchScreen_string_file);

                for (Map.Entry<String, String> keyValEntity : keyValMap.entrySet()) {
                    String key = keyValEntity.getKey();
                    String value = keyValEntity.getValue();
                    String annotation = Optional.ofNullable(zhCNMap).map(v -> v.get(key)).orElse("");
                    String line = String.format(formatIOS, key, value, annotation);
                    if (StringUtils.isBlank(value.trim())) {
                        keyValList.add("");
                        keyValList.add("// " + key);
                    } else {
                        keyValList.add(line);
                    }
                }
                writeToFile(launchScreen_string_file, keyValList);


                // 中文 - language
                if (zhCNMap != null && !zhCNMap.isEmpty()) {
                    String refZhCN = langFolder + "ref_zh_CN.strings";
                    File refZhCN_file = new File(refZhCN);
                    createFile(refZhCN_file);


                }

            } else {
                String langFolder = outputFolder + language + "/";
                String launchScreen_string = langFolder + "strings.xml";
                File launchScreen_string_file = new File(launchScreen_string);
                createFile(launchScreen_string_file);

                String xmlHead = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
                String resources = "<resources>";
                keyValList.add(xmlHead);
                keyValList.add(resources);

                for (Map.Entry<String, String> keyValEntity : keyValMap.entrySet()) {
                    String key = keyValEntity.getKey();
                    String value = keyValEntity.getValue();
                    String line = String.format(formatAndroid, key, value);
                    if (StringUtils.isBlank(value.trim())) {
                        keyValList.add("");
                    } else {
                        keyValList.add(line);
                    }
                }
                String resourcesClose = "</resources>";
                keyValList.add(resourcesClose);
                writeToFile(launchScreen_string_file, keyValList);
            }
        }

        if (!refZhCNMap.isEmpty()) {
            for (Map.Entry<String, Map<String, String>> entry : refZhCNMap.entrySet()) {
                String language = entry.getKey();
                List<String> keyValList = new ArrayList<>();
                Map<String, String> keyValMap = entry.getValue();

                if (type == 1) {
                    String langFolder = outputFolder + language + ".lproj/";

                    String refZhCN = langFolder + "ref_zh_CN.strings";
                    File refZhCN_file = new File(refZhCN);
                    createFile(refZhCN_file);

                    for (Map.Entry<String, String> keyValEntity : keyValMap.entrySet()) {
                        String key = keyValEntity.getKey();
                        String value = keyValEntity.getValue();
                        String line = String.format("\"%s\" = \"%s\";", key, value);
                        if (StringUtils.isBlank(value.trim())) {
                            keyValList.add("");
                        } else {
                            keyValList.add(line);
                        }
                    }
                    writeToFile(refZhCN_file, keyValList);

                }
            }

            // wifi_config_01 : 确认
            Map<String, String> refMap = readRefFile(refFile);
            // 确认 : XX
            for (Map.Entry<String, Map<String, String>> entry : refZhCNMap.entrySet()) {
                if (type != 1) {
                    break;
                }

                String language = entry.getKey();
                List<String> keyValList = new ArrayList<>();
                Map<String, String> keyValMap = entry.getValue();

                String langFolder = outputFolder + language + ".lproj/";
                String refZhCN = langFolder + "ref_result.strings";
                File refZhCN_file = new File(refZhCN);
                createFile(refZhCN_file);

                for (Map.Entry<String, String> refEntry : refMap.entrySet()) {
                    String key = refEntry.getKey();
                    String value = refEntry.getValue();

                    String languageVal = keyValMap.entrySet().stream()
                            .filter(e -> Objects.equals(value, e.getKey()))
                            .map(Map.Entry::getValue)
                            .distinct()
                            .collect(Collectors.joining("/"));

                    if (StringUtils.isBlank(languageVal)) {
                        languageVal = "";
                    }

                    String line = String.format("\"%s\" = \"%s\"; // %s", key, languageVal, value);
                    log.info("line: {}", line);
                    keyValList.add(line);
                }
                writeToFile(refZhCN_file, keyValList);
            }
        }


        File outputFolderFile = createFile(outputFolder);

        String outputZipPath = OUTPUT_FOLDER + timeTag + "_" + threadName + "/" + name + ".zip";
        File zipFile = createFile(outputZipPath);

        zip(name, outputFolderFile, zipFile);

        download(zipFile, request, response);

        deleteFolder(outputFolderFile);
        zipFile.delete();
    }

    private void writeToFile(File file, List<String> textList) {
        try (FileWriter fileWriter = new FileWriter(file);
             BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);) {
            for (String text : textList) {
                bufferedWriter.write(text);
                bufferedWriter.newLine();
            }
            bufferedWriter.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void zip(String name, File outputFile, File zipFile) throws IOException {
        log.info("outputFile = {}, zipFile = {}", outputFile.getAbsolutePath(), zipFile.getAbsolutePath());
        FileOutputStream fis = new FileOutputStream(zipFile);
        ZipOutputStream zos = new ZipOutputStream(fis);
        if (outputFile.exists() && outputFile.isDirectory()) {
            zipFolder(name, outputFile, zos);
        } else {
            zipFile(name, outputFile, zos);
        }
        zos.close();
        fis.close();
    }

    private void zipFolder(String name, File outputFile, ZipOutputStream zos) throws IOException {
        log.info("zipFolder --> path: {}", outputFile.getAbsolutePath());
        if (outputFile.isFile()) {
            zipFile(name, outputFile, zos);
            return;
        }

        for (File file : outputFile.listFiles()) {
            zipFolder(name, file, zos);
        }
    }

    private void zipFile(String name, File outputFile, ZipOutputStream zos) throws IOException {
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(outputFile));
        String path = outputFile.getAbsolutePath().substring(outputFile.getAbsolutePath().indexOf(name) + name.length() + 1);
        log.info("zipFile --> path: {}", path);
        ZipEntry zipEntry = new ZipEntry(path);
        zos.putNextEntry(zipEntry);

        byte[] buff = new byte[2048];
        int bytesRead;
        while ((bytesRead = bis.read(buff, 0, buff.length)) != -1) {
            zos.write(buff, 0, bytesRead);
        }
        bis.close();
        zos.closeEntry();
    }

    private void createFile(File file) throws IOException {
        if (file.exists()) {
            return;
        }

        if (file.isDirectory()) {
            file.mkdirs();
            return;
        }

        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }

        file.createNewFile();
    }

    private File createFile(String path) throws IOException {
        File file = new File(path);
        if (file.exists()) {
            return file;
        }

        if (file.isDirectory()) {
            file.mkdirs();
            return file;
        }

        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }

        file.createNewFile();
        return file;
    }

    private void deleteFolder(File file) {
        if (!file.exists()) {
            return;
        }
        if (file.isFile()) {
            file.delete();
            return;
        }
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File subFile : files) {
                deleteFolder(subFile);
            }
            file.delete();
        }
    }

    /**
     * 将文件下载到客户端
     *
     * @param file
     * @param request
     * @param response
     * @throws IOException
     */
    public void download(File file, HttpServletRequest request, HttpServletResponse response) throws IOException {
        String fileName = file.getName();
        response.setCharacterEncoding("UTF-8");
        String agent = request.getHeader("User-Agent").toUpperCase();
        if ((agent.indexOf("MSIE") > 0) || ((agent.contains("RV")) && (!agent.contains("FIREFOX")))) {
            fileName = URLEncoder.encode(fileName, "UTF-8");
        } else {
            fileName = new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8);
        }
        String contentType = Files.probeContentType(Paths.get(file.toURI()));
        response.setContentType("application/octet-stream");
        response.setContentType(contentType);

        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + fileName);
        response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(file.length()));
        @Cleanup
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
        @Cleanup
        BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream());

        byte[] buff = new byte[2048];
        int bytesRead;
        while (-1 != (bytesRead = bis.read(buff, 0, buff.length))) {
            bos.write(buff, 0, bytesRead);
        }
        bos.close();
        bis.close();
    }

    public void download(String fileName, InputStream is, HttpServletRequest request, HttpServletResponse response) throws IOException {
        request.setCharacterEncoding("UTF-8");
        String agent = request.getHeader("User-Agent").toUpperCase();
        if ((agent.indexOf("MSIE") > 0) || ((agent.contains("RV")) && (!agent.contains("FIREFOX")))) {
            fileName = URLEncoder.encode(fileName, "UTF-8");
        } else {
            fileName = new String(fileName.getBytes(StandardCharsets.UTF_8), "ISO8859-1");
        }
        response.setContentType("application/octet-stream");
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + fileName);

        @Cleanup
        BufferedInputStream bis = new BufferedInputStream(is);
        @Cleanup
        BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream());

        byte[] buff = new byte[2048];
        int bytesRead;
        while (-1 != (bytesRead = bis.read(buff, 0, buff.length))) {
            bos.write(buff, 0, bytesRead);
        }
        bos.close();
        bis.close();
    }
}
